<template>
  <div>
    <el-upload
      class="avatar-uploader el-upload--text"
      :action="''"
      :show-file-list="false"
      :http-request="handleChunkUpload"
      :before-upload="beforeUploadVideo"
      :on-progress="handleProgress"
      :headers="headers"
      :disabled="uploading"
    >
      <!-- 视频预览 -->
      <video
        v-if="firstVideo && firstVideo!=='' && !uploading"
        :src="firstVideo"
        class="avatar"
        controls="controls"
      >
        您的浏览器不支持视频播放
      </video>
      <!-- 上传按钮 -->
      <i
        v-else-if="!uploading"
        class="el-icon-plus avatar-uploader-icon"
      ></i>
      <!-- 上传/合并进度面板 -->
      <div v-if="uploading" class="upload-progress-container">
        <el-progress
          type="line"
          :percentage="displayPercent"
          style="margin-bottom: 10px; transition: none;"
          :stroke-width="6"
          :status="isMerging ? 'info' : ''"
        ></el-progress>
        <div class="upload-controls">
          <el-button
            @click="toggleUpload"
            size="mini"
            :icon="paused ? 'el-icon-play' : 'el-icon-pause'"
            :disabled="isAllChunksCompleted || isMerging"
          >
            {{ paused ? '继续' : '暂停' }}
          </el-button>
          <el-button
            @click="cancelUpload"
            size="mini"
            type="danger"
            icon="el-icon-delete"
            :disabled="isMerging"
          >
            取消
          </el-button>
        </div>
        <div class="upload-info">
          <span v-if="isMerging" class="merge-tip">正在合并视频，请勿关闭...</span>
          <template v-else>
            已上传: {{ displayUploadedSize }} / {{ displayTotalSize }}
            <span v-if="!isMerging" class="speed-tip">速度: {{ displaySpeed }}</span>
            <span style="margin-left:10px;">
              并发数: {{ currentConcurrency }}/{{ concurrency }}
            </span>
          </template>
        </div>
      </div>
    </el-upload>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { Message } from 'element-ui';
import request from '@/utils/request';

// 与vue.config.js中的后端地址保持一致
const BASE_API = 'http://localhost:9080';

// 简易防抖函数
const debounce = (fn, delay = 100) => {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
};

export default {
  name: "VideoChunkUpload",
  props: {
    value: {
      type: String,
      default: ""
    },
    chunkSize: {
      type: Number,
      default: 10 * 1024 * 1024 // 10MB分片
    },
    concurrency: {
      type: Number,
      default: 3 // 并发数（外部可通过:concurrency修改）
    }
  },
  data() {
    return {
      firstVideo: "",
      uploading: false,
      paused: false,
      isMerging: false,
      isMergeTriggered: false, // 新增：标记合并是否已触发，防止重复调用
      // 真实数据
      uploadPercent: 0,
      uploadedSize: 0,
      totalSize: 0,
      totalChunks: 0,
      file: null,
      fileId: null,
      speed: 0,
      lastUploadedSize: 0,
      timer: null,
      // 显示数据
      displayPercent: 0,
      displayUploadedSize: "0 B",
      displayTotalSize: "0 B",
      displaySpeed: "0 KB/s",
      // 并行上传核心状态
      uploadedChunks: [], // 已完成分片索引
      uploadingChunks: [], // 正在上传的分片索引（关键：判断是否还有未完成的上传）
      failedChunks: [],
      currentConcurrency: 0,
      // 其他配置
      baseUrl: '/chunk/file/upload',
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      uploadPanelKey: 1,
      maxRetries: 3,
      mergeTimeout: 600000
    };
  },
  computed: {
    // 核心修改：判断所有分片完成的条件 = 已完成分片数 >= 总分片数 + 没有正在上传的分片
    isAllChunksCompleted() {
      return (
        this.uploadedChunks.length >= this.totalChunks &&
        this.uploadingChunks.length === 0 && // 新增：确保没有正在上传的分片
        !this.failedChunks.length // 新增：确保没有失败的分片（失败分片需处理后才能合并）
      );
    },
    nextChunkIndex() {
      if (this.isMerging || this.isMergeTriggered) return -1; // 合并中/已触发合并，禁止新分片

      // 优先重试失败分片
      if (this.failedChunks.length > 0) {
        return this.failedChunks.shift();
      }
      // 寻找未上传的分片
      for (let i = 0; i < this.totalChunks; i++) {
        if (!this.uploadedChunks.includes(i) && !this.uploadingChunks.includes(i)) {
          return i;
        }
      }
      return -1;
    }
  },
  watch: {
    value: {
      handler(val) {
        this.firstVideo = this.formatCompleteVideoUrl(val);
      },
      immediate: true,
      deep: true
    },
    uploadPercent: debounce(function(newVal) {
      this.displayPercent = Math.min(Math.round(newVal), 100);
    }),
    uploadedSize: debounce(function(newVal) {
      this.displayUploadedSize = this.formatSize(newVal);
    }, 200),
    speed: debounce(function(newVal) {
      this.displaySpeed = this.formatSpeed(newVal);
    }, 300),
    currentConcurrency: function(newVal) {
      if (!this.paused && !this.isMerging && !this.isMergeTriggered && newVal < this.concurrency && !this.isAllChunksCompleted) {
        this.startNextChunk();
      }
    },
    // 合并触发：只有满足所有条件才触发，且只触发一次
    isAllChunksCompleted: function(newVal) {
      if (newVal && !this.paused && !this.isMerging && !this.isMergeTriggered) {
        this.mergeChunks();
      }
    }
  },
  methods: {
    formatSize(bytes) {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    },

    formatSpeed(bytesPerSecond) {
      const k = 1024;
      const speeds = ['B/s', 'KB/s', 'MB/s'];
      if (bytesPerSecond === 0) return '0 KB/s';
      const i = Math.floor(Math.log(bytesPerSecond) / Math.log(k));
      const value = Math.max(parseFloat((bytesPerSecond / Math.pow(k, i)).toFixed(2)), 0.01);
      return `${value} ${speeds[i]}`;
    },

    formatCompleteVideoUrl(relativeUrl) {
      if (!relativeUrl) return '';
      if (relativeUrl.startsWith('http://') || relativeUrl.startsWith('https://')) {
        return relativeUrl;
      }
      const base = BASE_API.endsWith('/') ? BASE_API.slice(0, -1) : BASE_API;
      const path = relativeUrl.startsWith('/') ? relativeUrl : `/${relativeUrl}`;
      return `${base}${path}`;
    },

    toggleUpload() {
      if (this.isAllChunksCompleted || this.isMerging) return;
      this.paused = !this.paused;
      if (!this.paused) {
        this.lastUploadedSize = this.uploadedSize;
        if (!this.timer) {
          this.startSpeedTimer();
        }
        // 继续上传时，重置合并触发标记
        this.isMergeTriggered = false;
        while (!this.isMerging && !this.isMergeTriggered && this.currentConcurrency < this.concurrency && this.nextChunkIndex !== -1) {
          this.startNextChunk();
        }
      }
    },

    startSpeedTimer() {
      if (this.timer) clearInterval(this.timer);
      this.timer = setInterval(() => {
        if (this.isMerging || this.paused) {
          this.speed = 0;
          return;
        }
        const currentSpeed = this.uploadedSize - this.lastUploadedSize;
        this.speed = Math.max(currentSpeed, 0);
        this.lastUploadedSize = this.uploadedSize;
      }, 1000);
    },

    cancelUpload() {
      this.paused = true;
      this.isMerging = false;
      this.isMergeTriggered = false; // 重置合并触发标记
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }

      // 取消所有正在上传的请求（若request支持取消令牌）
      Object.keys(this.uploadingChunksObj || {}).forEach(idx => {
        const cancelToken = this.uploadingChunksObj[idx]?.cancelToken;
        cancelToken && cancelToken();
      });

      if (this.fileId) {
        request({
          url: `${this.baseUrl}/cancel`,
          method: 'post',
          data: { fileId: this.fileId },
          timeout: 10000
        }).catch(err => console.log('取消上传失败:', err));
      }

      // 重置所有状态
      this.uploading = false;
      this.uploadPercent = 0;
      this.uploadedSize = 0;
      this.lastUploadedSize = 0;
      this.speed = 0;
      this.displaySpeed = "0 KB/s";
      this.uploadedChunks = [];
      this.uploadingChunks = [];
      this.failedChunks = [];
      this.currentConcurrency = 0;
      this.file = null;
      this.fileId = null;
      this.displayPercent = 0;
      this.displayUploadedSize = "0 B";
      this.displayTotalSize = "0 B";
      this.uploadPanelKey++;
    },

    beforeUploadVideo(file) {
      // 格式校验
      const acceptTypes = ["video/mp4", "video/ogg", "video/flv", "video/avi", "video/wmv", "video/rmvb", "video/mov"];
      const fileExt = file.name.split('.').pop().toLowerCase();
      if (!acceptTypes.includes(file.type) && !acceptTypes.includes(fileExt)) {
        Message.error("请上传正确的视频格式（mp4/ogg/flv/avi/wmv/rmvb/mov）");
        return false;
      }

      // 初始化状态（重置合并相关标记）
      this.isMerging = false;
      this.isMergeTriggered = false;
      this.file = file;
      this.totalSize = file.size;
      this.totalChunks = Math.ceil(file.size / this.chunkSize);
      this.uploadedSize = 0;
      this.lastUploadedSize = 0;
      this.uploadedChunks = [];
      this.uploadingChunks = [];
      this.failedChunks = [];
      this.currentConcurrency = 0;
      this.uploadPercent = 0;
      this.uploading = true;
      this.paused = false;
      // 显示数据初始化
      this.displayTotalSize = this.formatSize(file.size);
      this.displayUploadedSize = "0 B";
      this.displayPercent = 0;
      this.speed = 0;
      this.displaySpeed = "0 KB/s";
      this.uploadPanelKey++;

      this.startSpeedTimer();

      // 获取fileId并启动上传
      this.getFileId(file)
        .then(fileId => {
          this.fileId = fileId;
          for (let i = 0; !this.isMerging && !this.isMergeTriggered && i < this.concurrency; i++) {
            this.startNextChunk();
          }
        })
        .catch(err => {
          console.error('获取fileId失败:', err);
          Message.error(`初始化上传失败: ${err.message || '未知错误'}`);
          this.cancelUpload();
        });

      return false;
    },

    getFileId(file) {
      return new Promise((resolve, reject) => {
        request({
          url: `${this.baseUrl}/get-file-id`,
          method: 'post',
          data: {
            fileName: file.name,
            fileSize: file.size,
            fileType: file.type
          }
        }).then(res => {
          if (res.code !== 200) {
            return reject(new Error(`接口错误: ${res.msg || '未知错误'}`));
          }
          const validFileId = res.data.fileId || res.data;
          if (!validFileId || typeof validFileId !== 'string') {
            return reject(new Error('后端未返回有效fileId'));
          }
          resolve(validFileId);
        }).catch(err => {
          reject(new Error(`请求失败: ${err.message || '网络错误'}`));
        });
      });
    },

    handleChunkUpload() {},
    handleProgress() {},

    // 启动下一个分片上传（核心：最后一个分片完成后二次校验合并条件）
    startNextChunk() {
      if (this.paused || this.isAllChunksCompleted || this.isMerging || this.isMergeTriggered) {
        return;
      }

      const chunkIndex = this.nextChunkIndex;
      if (chunkIndex === -1) return;

      // 标记正在上传
      this.uploadingChunks.push(chunkIndex);
      this.currentConcurrency++;

      this.uploadChunk(chunkIndex)
        .then(() => {
          // 成功：添加到已完成列表
          if (!this.uploadedChunks.includes(chunkIndex)) {
            this.uploadedChunks.push(chunkIndex);
          }
          this.calcUploadPercent();
        })
        .catch((retries) => {
          // 失败：超过重试次数则暂停，等待用户处理
          if (retries >= this.maxRetries) {
            Message.error(`分片 ${chunkIndex + 1} 上传失败（已重试${this.maxRetries}次），请检查网络后重试`);
            this.paused = true;
            this.isMergeTriggered = false; // 失败后重置合并标记
          } else {
            this.failedChunks.push(chunkIndex);
          }
        })
        .finally(() => {
          // 清理正在上传的分片状态
          this.uploadingChunks = this.uploadingChunks.filter(idx => idx !== chunkIndex);
          this.currentConcurrency--;

          // 核心新增：最后一个分片完成后，二次校验合并条件（防止watch延迟）
          if (!this.paused && !this.isMerging && !this.isMergeTriggered) {
            if (this.uploadedChunks.length >= this.totalChunks && this.uploadingChunks.length === 0 && !this.failedChunks.length) {
              this.mergeChunks();
            }
          }
        });
    },

    uploadChunk(chunkIndex, retries = 0) {
      return new Promise((resolve, reject) => {
        if (this.isMerging || this.isMergeTriggered) {
          return reject('merge_started');
        }

        const start = chunkIndex * this.chunkSize;
        const end = Math.min(start + this.chunkSize, this.totalSize);
        const chunk = this.file.slice(start, end);
        const currentChunkSize = end - start;

        const formData = new FormData();
        formData.append('fileId', this.fileId);
        formData.append('chunk', chunkIndex);
        formData.append('file', chunk);

        request({
          url: `${this.baseUrl}/chunk`,
          method: 'post',
          data: formData,
          headers: {
            ...this.headers,
            'Content-Type': 'multipart/form-data'
          },
          timeout: 300000,
          onUploadProgress: (progressEvent) => {
            if (this.paused || this.isMerging || this.isMergeTriggered) return;

            const chunkProgress = progressEvent.loaded / progressEvent.total || 0;
            const completedSize = this.uploadedChunks.length * this.chunkSize;
            const chunkUploaded = Math.floor(chunkProgress * currentChunkSize);
            this.uploadedSize = completedSize + chunkUploaded;
            this.calcUploadPercent();
          }
        }).then(res => {
          if (res.code === 200) {
            resolve();
          } else if (res.msg?.includes('已存在') || res.msg?.includes('重复')) {
            resolve();
          } else {
            if (retries < this.maxRetries && !this.isMerging && !this.isMergeTriggered) {
              setTimeout(() => {
                this.uploadChunk(chunkIndex, retries + 1).then(resolve).catch(reject);
              }, 1000 * (retries + 1));
            } else {
              reject(retries);
            }
          }
        }).catch(err => {
          if (retries < this.maxRetries && !this.isMerging && !this.isMergeTriggered) {
            setTimeout(() => {
              this.uploadChunk(chunkIndex, retries + 1).then(resolve).catch(reject);
            }, 1000 * (retries + 1));
          } else {
            reject(retries);
          }
        });
      });
    },

    calcUploadPercent() {
      if (this.isMerging) {
        this.uploadPercent = 100;
        this.displayPercent = 100;
        return;
      }
      this.uploadPercent = Math.min((this.uploadedSize / this.totalSize) * 100, 100);
    },

    // 合并分片（核心：标记合并已触发，防止重复调用）
    mergeChunks() {
      if (!this.fileId || this.isMerging || this.isMergeTriggered) {
        return;
      }

      // 标记合并已触发，禁止再次调用
      this.isMergeTriggered = true;
      this.isMerging = true;
      this.paused = false;
      this.currentConcurrency = -1;
      this.speed = 0;
      this.displaySpeed = "0 KB/s";
      Message.info('正在合并视频，请稍候...');

      request({
        url: `${this.baseUrl}/merge`,
        method: 'post',
        data: {
          fileId: this.fileId,
          fileName: this.file.name,
          chunks: this.totalChunks
        },
        timeout: this.mergeTimeout
      }).then(res => {
        if (res.code === 200 && res?.url) {
          Message.success('视频上传成功');
          const relativeUrl = res.url;
          const completeVideoUrl = this.formatCompleteVideoUrl(relativeUrl);
          this.firstVideo = completeVideoUrl;
          this.$emit("input", completeVideoUrl);
          this.$emit("upload-complete", completeVideoUrl);
        } else {
          throw new Error(`文件合并失败: ${res.msg || '未返回有效视频URL'}`);
        }
      }).catch(err => {
        Message.error(`文件合并出错: ${err.message || '网络超时'}`);
        this.isMerging = false;
        this.isMergeTriggered = false; // 合并失败，允许重试
        this.currentConcurrency = 0;
        this.paused = true;
        this.startSpeedTimer();
      }).finally(() => {
        clearInterval(this.timer);
        this.timer = null;
        this.uploading = false;
        if (!this.isMerging && !this.paused) {
          this.uploadedChunks = [];
          this.uploadingChunks = [];
          this.failedChunks = [];
        }
      });
    }
  },
  beforeDestroy() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
    this.isMerging = false;
    this.isMergeTriggered = false;
    this.uploading = false;
    if (this.uploading && this.fileId) {
      request({
        url: `${this.baseUrl}/cancel`,
        method: 'post',
        data: { fileId: this.fileId }
      }).catch(() => {});
    }
  }
};
</script>

<style scoped>
.avatar-uploader {
  position: relative;
  overflow: hidden;
  animation: none !important;
}

.avatar-uploader-icon {
  border: 1px dashed #d9d9d9;
  font-size: 28px;
  color: #8c939d;
  width: 300px;
  height: 178px;
  line-height: 178px;
  text-align: center;
  transition: border-color 0.3s ease;
}

.avatar-uploader .el-upload:hover .avatar-uploader-icon {
  border-color: #409eff;
}

.avatar {
  width: 300px;
  height: 178px;
  display: block;
  object-fit: contain;
  background: #f5f5f5;
}

.upload-progress-container {
  width: 300px;
  padding: 15px;
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  background: #fff;
  height: 120px;
  box-sizing: border-box;
}

.upload-controls {
  margin-bottom: 8px;
  display: flex;
  gap: 8px;
}

.upload-info {
  font-size: 12px;
  color: #666;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  line-height: 1.5;
}

.merge-tip {
  color: #409eff;
  font-weight: 500;
}

.speed-tip {
  color: #666;
  margin-left: 8px;
}

.el-progress-bar__inner {
  transition: width 0s !important;
}
</style>
